/*
Copyright (c) 2013 Mihail Volkov

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include "sample_units.h"
#include <cassert>
#include <cstdarg>
#include <cstdio>
#include <windows.h> // for FindFirstFile/FindNextFile

#ifndef DISABLE_HTMD

//
// HtMdTestConsole
//

HtMdTestConsole *HtMdTestConsole::spInstance = NULL;

HtMdTestConsole::HtMdTestConsole ():
	HtMdQuickStartUnit (MAX_BUFFER_SIZE,MAX_BUFFER_SIZE)
{
	assert (spInstance==NULL);
	spInstance = this;
	mCurChannel = mChansEnabled.end ();
}

HtMdTestConsole::~HtMdTestConsole ()
{
	assert (spInstance==this);
	spInstance = NULL;
}

// commands

void HtMdTestConsole::registerCommand (const std::string &name,CommandBase *pCmd)
{
	assert (spInstance!=NULL);
	spInstance->mCommands[name] = pCmd;
}

void HtMdTestConsole::unregisterCommand (const std::string &name,CommandBase *pCmd)
{
	assert (spInstance!=NULL);
	if (spInstance->mCommands[name]==pCmd)
		spInstance->mCommands.erase (name);
}

// printfs
static char printfBuf[HtMdTestConsole::MAX_BUFFER_SIZE+1];
void HtMdTestConsole::printf (const char *format,...)
{
	assert (spInstance!=NULL);
	HtMdTestConsole &self = *spInstance;

	if (!isConnected () || !self.isClientReady ()) return;

	if (self.mCurChannel==self.mChansEnabled.end () ||
		!self.mCurChannel->second) return; // channel disabled

	va_list args;
	va_start (args,format);
	int bytes = _vsnprintf (printfBuf,MAX_BUFFER_SIZE+1,format,args);
	if (bytes==-1 || bytes>MAX_BUFFER_SIZE)
		self.emitOutputOverflowWarning ();
	else
		self.sendString (self.mCurChannel->first+":"+printfBuf);
}

void HtMdTestConsole::setPrintfChannel (const char *channel)
{
	assert (spInstance!=NULL);
	HtMdTestConsole &self = *spInstance;

	if (self.mCurChannel==self.mChansEnabled.end () ||
		self.mCurChannel->first!=channel)
	{
		std::map<std::string,bool>::iterator it =
			self.mChansEnabled.find (channel);
		if (it==self.mChansEnabled.end ())
			// by default, a channel is enabled
			self.mChansEnabled[channel] = true;
		self.mCurChannel =
			self.mChansEnabled.find (channel);
	}
}

void HtMdTestConsole::onClientConnected ()
{
	HtMdQuickStartUnit::onClientConnected ();
	outUnitOpenWindow ("htmd:/test_console.html");
}

void HtMdTestConsole::onClientDisconnected ()
{
	HtMdQuickStartUnit::onClientDisconnected ();
	// it seems like nothing is left to do here
}

void HtMdTestConsole::onReceiveString
	(const std::string &cmd,bool overflowTruncated)
{
	// TODO: do it
	//::printf ("Client says '%s'%s to the console\n",
		//cmd.c_str (),overflowTruncated? " (truncated)" : "");

	// parse out the command
	unsigned u=0,n=cmd.length ();
	for (u=0; u<n && cmd[u]>' '; u++) {}

	// the command itself
	std::string theCmd = cmd.substr (0,u);

	// skip ws
	for (; u<n && cmd[u]<=' '; u++) {}
	std::string theParams = cmd.substr (u);

	// TODO: decypher '?' and streams
	if (theCmd=="?")
	{
		HTMD_PRINTF (HATUL_SERVER,("Built-in commands:"));
		HTMD_PRINTF (HATUL_SERVER,("? - this help"));
		HTMD_PRINTF (HATUL_SERVER,("/on STREAM_NAME - enable stream STREAM_NAME (except HATUL_CLIENT)"));
		HTMD_PRINTF (HATUL_SERVER,("/off STREAM_NAME - disable stream STREAM_NAME (exc. HATUL_CLIENT)"));
		HTMD_PRINTF (HATUL_SERVER,("User commands:"));
		for (std::map<std::string,CommandBase *>::iterator itCmd=
			mCommands.begin (); itCmd!=mCommands.end (); ++itCmd)
			HTMD_PRINTF (HATUL_SERVER,("%s",itCmd->second->mHelp.c_str ()));
	}
	else if (theCmd=="/on")
		mChansEnabled[theParams] = true;
	else if (theCmd=="/off")
		mChansEnabled[theParams] = false;
	else
	{
		// decypher user commands
		std::map<std::string,CommandBase *>::iterator itCmd =
			mCommands.find (theCmd);
		if (itCmd!=mCommands.end ())
			(*itCmd->second) (theParams);
		else
			HTMD_PRINTF (HATUL_SERVER,(
			"Command '%s' is not currently registered",
			theCmd.c_str ()));
	}
}

std::string HtMdTestConsole::getSentOverflowWarningString ()
{
	return "** output buffer overflow **";
}

void HtMdTestConsole::onClientTurnIn ()
{
	HtMdQuickStartUnit::onClientTurnIn ();
	HTMD_PRINTF (HATUL_SERVER,
		("Test console\nPowered by Hatul Madan 1.0.0\nEnter ? or help for list of available commands (may change dynamically depending on application state)"));
}

void HtMdTestConsole::onClientSuspended ()
{
	HtMdQuickStartUnit::onClientSuspended ();
	// force re-open the unit window
	outUnitOpenWindow ("htmd:/test_console.html");
}

//
// HtMdTestFileBrowser
//

HtMdTestFileBrowser *HtMdTestFileBrowser::spInstance = NULL;

HtMdTestFileBrowser::HtMdTestFileBrowser (const std::string &basePath):
	mBasePath (basePath)
{
	assert (spInstance==NULL);
	spInstance = this;

	mFinished = !isConnected ();
}

HtMdTestFileBrowser::~HtMdTestFileBrowser ()
{
	assert (spInstance==this);
	spInstance = NULL;
}

bool HtMdTestFileBrowser::isFinished ()
{
	return mFinished;
}

void HtMdTestFileBrowser::onClientDisconnected ()
{
	mFinished = true;
	mFileUploader.cancel ();
	mFileDownloader.cancel ();
	HtMdQuickStartUnit::onClientDisconnected ();
}

void HtMdTestFileBrowser::onClientSuspended ()
{
	mFinished = true;
	mFileUploader.cancel ();
	mFileDownloader.cancel ();
	HtMdQuickStartUnit::onClientSuspended ();
}

void HtMdTestFileBrowser::onReceiveString (const std::string &cmd,bool)
{
	// parse out the command
	unsigned u=0,n=cmd.length ();
	for (u=0; u<n && cmd[u]!=':'; u++) {}

	// the command itself
	std::string theCmd = cmd.substr (0,u);

	// skip colon
	if (u<n) u++;
	std::string theParam = cmd.substr (u);

	// TODO: process the command
	if (theCmd=="refresh")
		refresh ("");
	else if (theCmd=="cdup")
	{
		// dir up (cd ..)
		if (!mPath.empty ()) mPath.pop_back ();
		refresh ("");
	}
	else if (theCmd=="cd")
	{
		// dir down (cd dir)
		// param is index of directory in the list
		int idx = 0;
		sscanf (theParam.c_str (),"%d",&idx);
		if (idx>=0 && idx<=(int)mDirs.size ())
			mPath.push_back (mDirs[idx]);
		refresh ("");
	}
	else if (theCmd=="upload")
	{
		mFileUploader.cancel ();
		mFileDownloader.cancel ();
		mFileUploader.prepareDir (getCurrentPath ());
		mFileUploader.beginUpload ("","All files (*)");
		mCurFileOp = FO_UPLOAD;
	}
	else if (theCmd=="download")
	{
		int idx = 0;
		sscanf (theParam.c_str (),"%d",&idx);
		if (idx>=0 && idx<=(int)mFiles.size ())
		{
			mFileUploader.cancel ();
			mFileDownloader.cancel ();
			mCurFileName = mFilesLatin[idx];
			mFileDownloader.beginDownload (
				getCurrentPath ()+"/"+mFiles[idx],"All files (*)");
			mCurFileOp = FO_DOWNLOAD;
		}
	}
}

void HtMdTestFileBrowser::onUpdate ()
{
	switch (mCurFileOp)
	{
	case FO_NONE: default: break;
	case FO_UPLOAD:
		if (!mFileUploader.isInProgress ())
		{
			if (mFileUploader.isFailed ())
				refresh (std::string ("red:Uploading of ")
				+mFileUploader.getFile ()+" failed - "
				+mFileUploader.getErrorString ());
			else
				refresh (std::string ("green:File ")
				+mFileUploader.getFile ()+" uploaded");
			mCurFileOp = FO_NONE;
		}
		break;
	case FO_DOWNLOAD:
		if (!mFileDownloader.isInProgress ())
		{
			if (mFileDownloader.isFailed ())
				refresh (std::string ("red:Downloading of ")
				+mCurFileName+" failed - "
				+mFileDownloader.getErrorString ());
			else
				refresh (std::string ("green:File ")
				+mCurFileName+" downloaded");
			mCurFileOp = FO_NONE;
		}
		break;
	}
}

std::string HtMdTestFileBrowser::getCurrentRootRelativePath ()
{
	std::string path;
	for (std::list<std::string>::iterator it=mPath.begin ();
		it!=mPath.end (); ++it)
		path += std::string ("/")+*it;
	return path;
}

std::string HtMdTestFileBrowser::getCurrentPath ()
{
	return mBasePath+getCurrentRootRelativePath ();
}

static std::string toLatin1 (const std::string &src)
{
	unsigned u,n = src.length ();
	std::string dst = src;
	for (u=0; u<n; u++)
		if ((unsigned char)dst[u]<32 || (unsigned char)dst[u]>127)
		dst[u] = '?';
	return dst;
}

void HtMdTestFileBrowser::refresh (const std::string &status)
{
	WIN32_FIND_DATAA findFileData;
	HANDLE hFind;

	sendString ("refresh_begin:");

	std::string mask = getCurrentPath ()+"/*";
	sendString (std::string ("curdir:")+getCurrentRootRelativePath ());

	mDirs.clear (); mDirsLatin.clear ();
	mFiles.clear (); mFilesLatin.clear ();

	hFind = FindFirstFileA (mask.c_str (),&findFileData);
	if (hFind!=INVALID_HANDLE_VALUE)
	{
		do
		{
			std::string name = findFileData.cFileName;
			if (name=="." || name=="..") continue;
			if (findFileData.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)
			{
				mDirs.push_back (name);
				std::string publicName = toLatin1 (name);
				mDirsLatin.push_back (publicName);
				sendString (std::string ("dir:")+publicName+"/");
			}
			else
			{
				mFiles.push_back (name);
				std::string publicName = toLatin1 (name);
				mFilesLatin.push_back (publicName);
				char fileSize[64];
				sprintf (fileSize,"%lu",findFileData.nFileSizeLow);
				sendString (std::string ("file:")+publicName
					+" ("+fileSize+")");
			}
		} while (FindNextFileA (hFind,&findFileData));
		FindClose (hFind);
	}

	sendString ("refresh_end:");
	sendString (std::string ("status:")+status);
}

void HtMdTestFileBrowser::onClientConnected ()
{
	HtMdQuickStartUnit::onClientConnected ();
	outUnitOpenWindow ("htmd:/test_filebrowser.html");
}

std::string HtMdTestFileBrowser::Uploader::acceptIncomingName
	(const std::string &initialName,const std::string &userName)
{
	mFile = toLatin1 (userName);
	return mDir+"/"+userName;
}

#endif // DISABLE_HTMD
